package com.company.finance.ddd.domain.core.voucher.rule;

import com.company.finance.ddd.common.FlagEnum;
import com.company.finance.ddd.domain.core.bill.aggregate.Bill;
import com.company.finance.ddd.domain.core.bill.enums.BillSubTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 本类是”凭证生成规则“的定义类, 其中 billSpecifications 用于指定 voucherType 所对应的单据(bill)标识.
 * 一个类型的单据会生成一种或多种类型的凭证。单据类型有25种（以后还会增加），而凭证类型有8中（以后增加的可能性不大）。
 * 所以本类采用以一个凭证类型对多种单据类型的设计，避免凭证生成规则类过多降低可维护性。
 * @author wanglei
 * @version $$Id: RuleDefinition, v 0.1 2021/2/3 14:49 wanglei Exp $$
 */
@Builder
@Getter
@ToString
public class RuleDefinition {

    private final static Map<String, RuleDefinition> OBJECT_CACHE = new ConcurrentHashMap<>();

    private final VoucherType voucherType;
    private final Set<BillSpecification> billSpecifications;

    private RuleDefinition(VoucherType voucherType, Set<BillSpecification> billSpecifications) {
        this.voucherType = voucherType;
        this.billSpecifications = billSpecifications;
    }

    public BillSpecification getMatchedBillSpecification(Bill bill) {
        for (BillSpecification billSpecification : billSpecifications) {
            if (billSpecification.getBillSubTypeEnum().subTypeCode.equals(bill.getSUB_TYPE_CODE())
                    && billSpecification.getPrepayment().equals(FlagEnum.getFlag(bill.getPREPAYMENT()))
                    && billSpecification.getRelateDeal().equals(FlagEnum.getFlag(bill.getRELATE_DEAL()))) {
                return billSpecification;
            }
        }
        throw new RuntimeException("No matched BillSpecification, billId -> "
                + bill.getBILL_NO() + " subTypeCode:" + bill.getSUB_TYPE_CODE()
                +  " prepayment:" + bill.getPREPAYMENT() +  " relate:" + bill.getRELATE_DEAL());
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private VoucherType voucherType;
        private Set<BillSpecification> billSpecifications;

        public Builder voucherType(VoucherType voucherType) {
            this.voucherType = voucherType;
            return this;
        }

        public Builder addBillSpecification(BillSpecification billSpecification) {
            if (null == billSpecifications) {
                billSpecifications = new HashSet<>();
            }
            if (!billSpecifications.add(billSpecification)) {
                throw new DuplicateRuleDefinitionException(MessageFormat.format("{0}{1}"
                        , voucherType, billSpecification.toString()));
            }
            return this;
        }

        public RuleDefinition build() {
            if (null == OBJECT_CACHE.get(voucherType.getCode())) {
                OBJECT_CACHE.put(voucherType.getCode(), new RuleDefinition(voucherType, billSpecifications));
            }
            return OBJECT_CACHE.get(voucherType.getCode());
        }
    }

    @ToString
    @Getter
    @AllArgsConstructor
    @lombok.Builder
    public static class BillSpecification {
        private final BillSubTypeEnum billSubTypeEnum;
        private final FlagEnum prepayment;
        private final FlagEnum relateDeal;
        /**
         * 单据明细对应的凭证生成规则
         */
        private final BillItemRule billItemRule;

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof BillSpecification)) {
                return false;
            }
            BillSpecification that = (BillSpecification) o;
            return billSubTypeEnum == that.billSubTypeEnum && prepayment == that.prepayment && relateDeal == that.relateDeal;
        }

        @Override
        public int hashCode() {
            return Objects.hash(billSubTypeEnum, prepayment, relateDeal);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof RuleDefinition)) {
            return false;
        }
        RuleDefinition that = (RuleDefinition) o;
        return Objects.equals(voucherType, that.voucherType) && Objects.equals(billSpecifications, that.billSpecifications);
    }

    @Override
    public int hashCode() {
        return Objects.hash(voucherType, billSpecifications);
    }

    public static class DuplicateRuleDefinitionException extends RuntimeException {
        public DuplicateRuleDefinitionException(String message) {
            super(message);
        }
    }
}
